package at.fhooe.engine.pathfinding;

import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import at.fhooe.engine.entity.Entity;
import at.fhooe.engine.event.EventListener;
import at.fhooe.engine.event.events.EntityMovedEvent;
import at.fhooe.engine.event.events.KeyDownEvent;
import at.fhooe.engine.event.events.MapObjectCreatedEvent;
import at.fhooe.engine.event.events.MapObjectDestroyedEvent;
import at.fhooe.engine.event.events.MouseButtonUpEvent;
import at.fhooe.engine.mapobject.MapObject;
import at.fhooe.engine.pandora.Game;

public class PathfindingManager implements KeyDownEvent.Listener/*,  MouseButtonUpEvent.Listener, EntityMovedEvent.Listener*/{
	
	private HashMap<String, MapObject> m_mapObjects;
	private ArrayList<MapObject> m_mapObjectsToEngage;
	private ArrayList<MapObject> m_mapObjectsToDisengage;
	private TileMap m_tileMap;
	
	
	private Point m_goal;
	private Point m_player;
	private AStar m_aStar;
	private SteeringStrategy m_ObjectToSteer;
	
	private double goalX;
	private double goalY;
	
	public PathfindingManager() {
		m_mapObjects = new HashMap<String, MapObject>();
		m_mapObjectsToDisengage = new ArrayList<MapObject>();
		m_mapObjectsToEngage = new ArrayList<MapObject>();
		m_tileMap = new TileMap(10, 10);
		m_goal = new Point(0,0);
		m_player = new Point(0,0);
		m_aStar = new AStar();
	}
	
	

	public void onKeyDownEvent(KeyDownEvent keyDownEvent) {
		if(keyDownEvent.getKeyCode() == KeyEvent.VK_1) {			// 1
			m_tileMap.drawMap();
		} else if(keyDownEvent.getKeyCode() == KeyEvent.VK_2) {
			Entity e = Game.getInstance().getEntityManager().getEntityWithId("ShipEntity");
			if(e == null) {
				System.out.println("Error");
			}else {
				double[] d = e.getDebug();
				System.out.println("Ship: X: " + d[0] + " | Y: " + d[1]);
			}
		}
	}
	
	public ArrayList<Point> calculateRoute(Point goal, Point start)
	{
		m_goal = goal;
		if(isGoalOnMapObject())
		{
			return null;
		}
		m_goal.x = (int)(m_goal.x * m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth()*0.7);
		m_goal.y = (int)(m_goal.y * m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight()*0.7);
		
		start.x = (int)(start.x * m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth()*0.7);
		start.y = (int)(start.y * m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight()*0.7);
		m_aStar.createLevelForAStar(m_tileMap.getMap(), start, m_goal);
		m_aStar.makeAllSteps();
		m_aStar.drawLevel();
		
		return m_aStar.getListOfWayPoints((double)Game.getInstance().getGraphicsSystem().getWidth()/0.7/m_tileMap.getXZoning(), (double)Game.getInstance().getGraphicsSystem().getHeight()/0.7/m_tileMap.getYZoning());
	}
	
	public ArrayList<Point> calculateOptimizedRoute(Point goal, Point start)
	{
		m_goal = goal;
		if(isGoalOnMapObject())
		{
			return null;
		}
		m_goal.x = (int)(m_goal.x * m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth()*0.7);
		m_goal.y = (int)(m_goal.y * m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight()*0.7);
		start.x = (int)(start.x * m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth()*0.7);
		start.y = (int)(start.y * m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight()*0.7);
		m_aStar.createLevelForAStar(m_tileMap.getMap(), start, m_goal);
		m_aStar.makeAllSteps();
		m_aStar.drawLevel();
		
		return m_aStar.getOptimizedListOfWayPoints((double)Game.getInstance().getGraphicsSystem().getWidth()/0.7/m_tileMap.getXZoning(), (double)Game.getInstance().getGraphicsSystem().getHeight()/0.7/m_tileMap.getYZoning());
	}
	
	private boolean isGoalOnMapObject() {
		for(MapObject m : m_mapObjects.values()) {
			double rectLeft = m.getPositionX();
			double rectRight = m.getPositionX()+m.getSize();
			double rectTop = m.getPositionY();
			double rectBottom = m.getPositionY() + m.getSize();
			
			double closestX = Math.max(rectLeft, Math.min(rectRight, m_goal.x));
			double closestY = Math.max(rectTop, Math.min(rectBottom, m_goal.y));
			
			double distanceX = m_goal.x - closestX;
			double distanceY = m_goal.y - closestY;
			
			double distanceSquared = distanceX * distanceX + distanceY * distanceY;
			if(distanceSquared < 10)
			{
				return true;
			}
		}
		return false;
	}

	public void onMouseButtonUpEvent(MouseButtonUpEvent mouseButtonUpEvent) {
		if(mouseButtonUpEvent.getButton() == MouseEvent.BUTTON1) {
			System.out.println("New Target: PosX: " + mouseButtonUpEvent.getPositionX() + " PosY: " + mouseButtonUpEvent.getPositionY());
			m_goal = new Point((int)(mouseButtonUpEvent.getPositionX()/0.7), (int)(mouseButtonUpEvent.getPositionY()/0.7)); 
			goalX = (mouseButtonUpEvent.getPositionX()/0.7);
			goalY = (mouseButtonUpEvent.getPositionY()/0.7);
			m_goal.x = (int)(m_goal.x * m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth()*0.7);
			m_goal.y = (int)(m_goal.y * m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight()*0.7);
			onNewTarget();
		}
	}
	
	private void onNewTarget() {
		m_aStar.createLevelForAStar(m_tileMap.getMap(), m_player, m_goal);
		m_aStar.makeAllSteps();
		m_aStar.drawLevel();
		
		if(m_ObjectToSteer != null) {
//			m_ObjectToSteer.followPoint(new Point((int)goalX, (int)goalY));
//			m_ObjectToSteer.followPath(m_aStar.getListOfWayPoints((double)Game.getInstance().getGraphicsSystem().getWidth()/0.7/m_tileMap.getXZoning() , (double)Game.getInstance().getGraphicsSystem().getHeight()/0.7/m_tileMap.getYZoning()  ));
//			m_ObjectToSteer.followPath(m_aStar.getOptimizedListOfWayPoints((double)Game.getInstance().getGraphicsSystem().getWidth()/0.7/m_tileMap.getXZoning() , (double)Game.getInstance().getGraphicsSystem().getHeight()/0.7/m_tileMap.getYZoning()  ));
		}
	}
	
	public synchronized void update() {
		for(MapObject mapObj: m_mapObjectsToEngage) {
			Game.getInstance().getEventManager().dispatchEvent(new MapObjectCreatedEvent(mapObj.getMapObjectId(), mapObj.getName(), mapObj.getPositionX(), mapObj.getPositionY(), mapObj.getSize()));
			
			mapObj.engage();
			m_mapObjects.put(mapObj.getName(), mapObj);
		}
		m_mapObjectsToEngage.clear();
		
		for(MapObject mapObj: m_mapObjectsToDisengage) {
			Game.getInstance().getEventManager().dispatchEvent(new MapObjectDestroyedEvent(mapObj.getMapObjectId(), mapObj.getName()));
			
			mapObj.disengage();
			m_mapObjects.remove(mapObj.getName());
		}
		m_mapObjectsToDisengage.clear();
		
		for (MapObject mapObj : m_mapObjects.values()) {
			mapObj.update();
		}
	}

	public synchronized void addMapObject(MapObject mapObj) {
		if(hasMapObject(mapObj.getName())) {
			throw new RuntimeException("MapObject with name: '" + mapObj.getName() + "' exists already.");
		}
		if(m_mapObjectsToEngage.contains(mapObj)) {
			throw new RuntimeException("MapObject with name: '" + mapObj.getName() + "' was already added.");
		}
		m_mapObjectsToEngage.add(mapObj);
	}
	
	public synchronized void removeMapObject(MapObject mapObj) {
		m_mapObjectsToDisengage.add(mapObj);
	}
	
	public synchronized void removeMapObject(String name) {
		MapObject mapObj = getMapObject(name);
		removeMapObject(mapObj);
	}
	
	public synchronized void removeAllMapObjects() {
		for(MapObject mapObj : m_mapObjects.values()) {
			removeMapObject(mapObj);
		}
	}
	
	public synchronized boolean hasMapObject(String name) {
		return m_mapObjects.containsKey(name);
	}
	
	public synchronized boolean hasMapObjectWithId(String mapObjectId) {
		Iterator<Map.Entry<String, MapObject>> it = m_mapObjects.entrySet().iterator();
		while(it.hasNext()) {
			MapObject mapObj = ((Map.Entry<String, MapObject>)it.next()).getValue();
			if(mapObj.getMapObjectId().compareTo(mapObjectId) == 0) {
				return true;
			}
		}
		return false;
	}
	
	public synchronized MapObject getMapObject(String name) {
		MapObject mapObj = m_mapObjects.get(name);
		
		return mapObj;
	}
	
	public ArrayList<MapObject> getMapObjectsWithId(String mapObjId) {
		ArrayList<MapObject> foundMapObjects = new ArrayList<MapObject>();
		
		Iterator<Map.Entry<String, MapObject>> it = m_mapObjects.entrySet().iterator();
		while (it.hasNext()) {
			MapObject mapObj = ((Map.Entry<String, MapObject>)it.next()).getValue();
			if (mapObj.getMapObjectId().compareTo(mapObjId) == 0)
				foundMapObjects.add(mapObj);
		}
		
		return foundMapObjects;
	}

	public int getMapObjectsCount() {
		return m_mapObjects.size();
	}
	
	public void clearMapObjectsToEngage() {
		m_mapObjectsToEngage.clear();
	}

	public void allLoaded() {
		m_tileMap.allLoaded();
		Game.getInstance().getEventManager().addListener(this, KeyDownEvent.TYPE);
//		Game.getInstance().getEventManager().addListener(this, MouseButtonUpEvent.TYPE);
//		Game.getInstance().getEventManager().addListener(this, EntityMovedEvent.TYPE);
	}

	public void onEntityMovedEvent(EntityMovedEvent e) {
		if(e.getEntityName().equals("shipEntity")) {
			m_player = new Point((int)(e.getNewPositionX()* m_tileMap.getXZoning() / (double)Game.getInstance().getGraphicsSystem().getWidth() * 0.7), (int)(e.getNewPositionY()* m_tileMap.getYZoning() / (double)Game.getInstance().getGraphicsSystem().getHeight() * 0.7));
		}
	}



	public void setObjectToSteer(SteeringStrategy steeringStrategy) {
		m_ObjectToSteer = steeringStrategy;
	}
}
